package lukeer.fencing;

import csproj.fencing.Move;
import csproj.fencing.Player;

import java.util.ArrayList;

/**
 * Player using MiniMax with alpha-beta pruning over a generated game search tree, incorporating a depth limiting
 * function.
 *
 * User: Luke Richardson lukeer, Wafa M'Barek wmbarek
 * Date: 06/10/12
 * Time: 16:09
 */
public class MiniMaxPlayer extends BasicPlayer implements Player, csproj.fencing.Piece {
    private Move bestMove;

    /**
     * Generates a move using MiniMax and places the move on the currentGameState board.
     *
     * @return move to be tested by opponent
     */
    public Move makeMove() {
        numberOfMoves++;
        //System.out.println("Remaining pieces: " + currentGameState.getEmptyPositions().size());
        TreeNode tree = new TreeNode(null, player, currentGameState, 0, null, getDepth(), null);
        try {
            generateMaxValue(tree);
        } catch (FencingException e) {
            System.err.println("Illegal move generated by MiniMax player");
            System.exit(1);
        }
        try {
            currentGameState.placePiece(bestMove);
        } catch (FencingException e) {
            System.err.println("Illegal move generated by MiniMax player");
            e.printStackTrace();
            System.exit(1);
        }
        return bestMove;
    }

    /**
     * Generate MIN value
     *
     * @param node  current node
     * @return utility
     * @throws FencingException if illegal move is made
     */
    private int generateMinValue(TreeNode node) throws FencingException {
        if (node.isTerminalState()) return utility(node);
        if (node.isMaxDepth()) return utilityNonFinal(node);
        int v = Integer.MAX_VALUE;

        while (node.hasNextMove()) {
            v = Math.min(v, generateMaxValue(node.generateNextChild()));

        }
        return v;
    }

    /**
     * Generate MAX value
     *
     * @param node  current node
     * @return utility
     * @throws FencingException if illegal move is made
     */
    private int generateMaxValue(TreeNode node) throws FencingException {
        if (node.isTerminalState()) return utility(node);
        if (node.isMaxDepth()) return utilityNonFinal(node);
        int v = Integer.MIN_VALUE;

        while (node.hasNextMove()) {
            TreeNode currentMove = node.generateNextChild();
            int minValue = generateMinValue(currentMove);
            if (minValue > v) {
                v = minValue;
                bestMove = currentMove.getBestMove();
            }

        }
        return v;
    }

    /**
     * For the input node (which should be in a terminal state). Return a value corresponding to the utility of the
     * terminal board. This will be either BLACK, WHITE or DRAW, dependant on the current player and the winner
     * of the board
     *
     * @param node A node of the game search tree in a terminal state
     * @return The utility value
     */
    private int utility(TreeNode node) throws FencingException {
        switch (node.getResult()) {
            case BLACK:
                if (player == BLACK) return 5;
                return -5;
            case WHITE:
                if (player == WHITE) return 5;
                return -5;
            case EMPTY:
                return 0;
        }
        throw new FencingException("MiniMax player error");
    }

    /**
     * For the input node (which should not be in a terminal state). Return a value corresponding to the utility of the
     * terminal board. This will be either BLACK, WHITE or DRAW, dependant on an evaluation function.
     *
     * @param node A node of the game search tree not in a terminal state
     * @return The utility value
     */
    private int utilityNonFinal(TreeNode node) {
        int totalBlack = 0;
        int totalWhite = 0;
        int largestSector = 0;
        int winnerOfBestSector = 0;

        Board b = node.getBoard();
        ArrayList<Sector> sectors = b.getSectors();
        for (Sector s : sectors) {
            totalBlack += s.getBlack();
            totalWhite += s.getWhite();
            if (s.size() > largestSector) {
                largestSector = s.size();
                winnerOfBestSector = s.getWinner();
            }

        }
        if (player == BLACK & totalBlack < totalWhite) return -1;
        if (player == WHITE & totalBlack > totalWhite) return -1;

        if (player == BLACK & totalBlack > totalWhite) {
            if (winnerOfBestSector == BLACK) return 1;
            if (winnerOfBestSector == WHITE) return 0;
        }

        if (player == WHITE & totalBlack < totalWhite) {
            if (winnerOfBestSector == WHITE) return 1;
            if (winnerOfBestSector == BLACK) return 0;
        }
        return 0;
    }

    /**
     * Depth limit function. Allows tree generation to run inside time limit.
     *
     * @return maximum depth of tree
     */
    private int getDepth() {
        int possibleMoves = MoveGenerator.generateMoves(currentGameState, player).size();
        if (possibleMoves > 2000) return 1;
        if (possibleMoves > 1000) return 1;
        if (possibleMoves > 50) return 2;
        if (possibleMoves > 30) return 3;
        if (possibleMoves > 20) return 5;
        return Integer.MAX_VALUE;
    }
}